/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _TANSU_UPNP_H_
#define _TANSU_UPNP_H_

#include "tansu_status.h"
#include "tansu_http.h"
#include "tansu_ssdp.h"

#define TANSU_NS_UPNP_DEVICE    "urn:schemas-upnp-org:device-1-0"
#define TANSU_NS_UPNP_SERVICE   "urn:schemas-upnp-org:service-1-0"
#define TANSU_NS_UPNP_CONTROL   "urn:schemas-upnp-org:control-1-0"
#define TANSU_NS_UPNP_EVENT     "urn:schemas-upnp-org:event-1-0"

#define TANSU_MAX_AGE_DEFAULT   1800

struct tansu_device;

struct tansu_service {
    STAILQ_ENTRY(tansu_service) next;
    char *type;
    char *sid;
    struct tdom_doc *scpd;
    struct evbuffer *buffer;
    void (*cb)(struct evhttp_request *,
               struct tansu_device *,
               struct tansu_service *,
               const char *,
               void *);
    void *cbarg;
};

STAILQ_HEAD(tansu_services, tansu_service);
STAILQ_HEAD(tansu_devices, tansu_device);

struct tansu_device {
    STAILQ_ENTRY(tansu_device) next;
    STAILQ_ENTRY(tansu_device) next_embedded;
    struct tdom_doc *dd;
    struct evbuffer *buffer;
    struct tansu_services services;
    struct tansu_devices devices;

    struct tansu_upnp_base *base;
    int max_age;
    struct event exp_ev;
    struct tansu_keyvals *headers;
};

struct tansu_upnp_base {
    struct tansu_nwif_listener *nwif_listener;
    struct tansu_ssdp_server *ssdp;
    struct tansu_ssdp_listener *ssdp_listener;
    struct event_base *evbase;
    struct evhttp *http;
    uint16_t http_port;
    struct tansu_devices devices;
    char *server_name;
};

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @return 0 when matched (version: dt1 >= dt2), or unmatched.
 */
int tansu_upnp_check_type(const char *dt1, const char *dt2);

int tansu_upnp_count_3_2d_k(const struct tansu_device *device);

int tansu_upnp_build_3_2d_k(const struct tansu_device *device,
                            const char **table,
                            int size);

tansu_status_t
tansu_dd_set_udn(struct tdom_doc *dd, const char *udn);

struct tdom_elem * tansu_dom_get_list_elem(struct tdom_elem *parent,
                                           const char *ns,
                                           const char *list_name,
                                           const char *elem_name,
                                           const char *field_name,
                                           const char *name);

#define tansu_dd_get_elem(elem, name, n) \
    tdom_get_elem((elem), (name), TANSU_NS_UPNP_DEVICE, (n))

#define tansu_scpd_get_elem(elem, name, n) \
    tdom_get_elem((elem), (name), TANSU_NS_UPNP_SERVICE, (n))

#define tansu_dd_get_elem_value(device_elem, name) \
    tdom_get_value(tansu_dd_get_elem((device_elem), (name), 0))

#define tansu_dd_get_device_value(root, name) \
    tansu_dd_get_elem_value(tansu_dd_get_elem(root, "device", 0), name)

#define tansu_dd_get_udn(dd) \
    tansu_dd_get_device_value((dd)->root, "UDN")

#define tansu_dd_get_device_type(dd) \
    tansu_dd_get_device_value((dd)->root, "deviceType")

#define tansu_scpd_get_elem_value(elem, name) \
    tdom_get_value(tansu_scpd_get_elem((elem), (name), 0))

#define tansu_dd_get_service(device_elem, serviceId) \
    tansu_dom_get_list_elem(device_elem, \
                            TANSU_NS_UPNP_DEVICE, \
                            "serviceList", \
                            "service", \
                            "serviceId", \
                            (serviceId))

#define tansu_dd_get_service_type(device_elem, serviceId) \
    tdom_get_value( \
        tansu_dd_get_elem( \
            tansu_dd_get_service(device_elem, (serviceId)), "serviceType", 0))

#define tansu_scpd_get_action(scpd, name) \
    tansu_dom_get_list_elem((scpd)->root, \
                            TANSU_NS_UPNP_SERVICE, \
                            "actionList", \
                            "action", \
                            "name", \
                            (name))

#define tansu_scpd_get_state_variable(scpd, name) \
    tansu_dom_get_list_elem((scpd)->root, \
                            TANSU_NS_UPNP_SERVICE, \
                            "serviceStateTable", \
                            "stateVariable", \
                            "name", \
                            (name))

#define tansu_soap_get_error(soap) \
    tdom_get_elem(tdom_get_elem(tdom_get_elem(tdom_get_elem((soap)->root, \
                  "Body", TDOM_SOAP_ENV_NS_URI, 0), \
                  "Fault", TDOM_SOAP_ENV_NS_URI, 0), \
                  "detail", NULL, 0), \
                  "UPnPError", TANSU_NS_UPNP_CONTROL, 0)

int
tansu_device_count_device_type(struct tansu_device *device, const char *st);

int
tansu_device_count_service_type(struct tansu_device *device, const char *st);

void
tansu_upnp_base_ssdp_cb(const struct tansu_ssdp_packet *packet, void *arg);

void tansu_upnp_base_http_cb(struct evhttp_request *req, void *arg);

tansu_status_t tansu_upnp_base_stop(struct tansu_upnp_base *base);

tansu_status_t
tansu_upnp_base_start(struct event_base *event_base,
                      struct tansu_upnp_base *base,
                      uint16_t http_port,
                      struct tansu_nwifs *nwifs);

struct tansu_upnp_base *tansu_upnp_base_new();

void tansu_upnp_base_delete(struct tansu_upnp_base *base);

tansu_status_t
tansu_upnp_base_add_device(struct tansu_upnp_base *base,
                           struct tansu_device *device);

void tansu_upnp_base_notify_all(struct tansu_upnp_base *base);

tansu_status_t tansu_upnp_base_notify(struct tansu_upnp_base *base,
                                      struct tansu_device *device);

struct tansu_service *
tansu_service_new(const char *type,
                  const char *sid,
                  struct tdom_doc *scpd,
                  void (*cb)(struct evhttp_request *,
                             struct tansu_device *,
                             struct tansu_service *,
                             const char *action,
                             void *),
                  void *cbarg);

void tansu_service_delete(struct tansu_service *service);

struct tansu_device *
tansu_device_new(struct tdom_doc *dd, const char *udn);

void
tansu_device_delete(struct tansu_device *device);

tansu_status_t
tansu_device_add_service(struct tansu_device *device,
                         const char *type,
                         const char *sid,
                         struct tdom_doc *scpd,
                         void (*cb)(struct evhttp_request *,
                                    struct tansu_device *,
                                    struct tansu_service *,
                                    const char *action,
                                    void *),
                         void *cbarg);

tansu_status_t
tansu_device_remove_service(struct tansu_device *device, const char *sid);

void
tansu_device_extend_exp(struct tansu_device *device);

void
tansu_device_soap_cb(struct evhttp_request *req,
                     struct tansu_device *device,
                     struct tansu_service *service);

void
tansu_device_gena_cb(struct evhttp_request *req,
                     struct tansu_device *device,
                     struct tansu_service *service);

void
tansu_device_http_cb(struct evhttp_request *req, void *arg, int len);

#ifdef __cplusplus
}
#endif

#endif /* _TANSU_UPNP_H_ */

